package com.example.study.jetpack.compose

import android.content.ContentValues.TAG
import android.os.Parcelable
import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Divider
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.mapSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.example.study.Greeting
import kotlinx.android.parcel.Parcelize

/**
 *  created by ws
 *   on 2022/3/2
 *   describe:https://blog.csdn.net/Mr_Tony/article/details/118858756?spm=1001.2014.3001.5502
 */
class Compose的State {
    /*对状态变化做出反应是Compose的核心。Compose应用程序通过调用Composable函数将数据转换为UI。
    * 如果数据发生变化，可以使用新数据调用这些函数，从而创建更新UI。Compose
    * 提供了用于观察应用程序数据变化的工具，这些工具会自动调用函数，这称为重构。
    * Compose还会查看单个可组合组件需要哪些数据，以便它只需要重新组合数据已更改的组件，并且
    * 可以跳过组合未受影响的组件
    * 在底层，Compose使用自定义Kotlin编译器插件，因此当底层数据发生变化时，可以重新调用
    * 可组合函数以更新UI层次结构
    * 例如：当你调用Gretting("Android")的MyScreenContent可组合功能，你是硬编码的
    * 输入（"android"）,所以Gretting将添加到UI树一次，永远不会改变，即使身体MyScreenContent
    * 被重构
    * 要将内部状态添加到可组合，请使用该mutableStateOf函数，他提供可组合的可变内存。
    * 为了不让每次重组都有不同的状态，请记住使用remember，而且，如果在屏幕上的不同位置
    * 有多个可组合实例，每个副本将获得自己的状态版本。可以将内部状态视为类中的私有变量
    * 可组合函数将自动订阅它。如果状态发生变化，读取这些字段的可组合项将被重新组合
    * 要点：compose将通过读取State对象自动重组界面
    *       如果你在compose中使用LiveData等其他可观察类型，应该先将其转换为State<T>，
    *       然后再使用诸如LiveData<T>.observeAsState()之类的可组合扩展函数在可组合项
    *       中读取它
    * 注意：在Compose中将可变对象（如ArrayList<T>或可变数据类）不能由Compose观察，因此Compose
    *       不能在它们发生变化时触发重组。建议使用可观察的数据存储器（如State<List<T>>和不可变得listOf（）），
    *       而不是使用不可观察的可变对象
    * 示例：*/
    @Composable
    fun counter1() {
        val count = remember { mutableStateOf(0) }
        Button(onClick = { count.value++ }) {
            Text("I've been clicked ${count.value} times")
        }
    }
    /*有状态组合和无状态组合
    * 在Compose中组合分为两种，有状态组合和无状态组合
    * 一个UI组合中包含了状态就是有状态组合，如歌不包含状态就是无状态组合：
    * 无状态组合：
    * @Composable
private fun body(){
    Text("===")
}

    * 有状态组合：
    * @Composable
fun counter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("I've been clicked $count times")
    }
}
    * */

    /*状态提升
    在可组合函数中，应该公开对调用函数有用的状态,因为这是可以使用或控制它的
    唯一方式——这个过程称为状态提升
    状态提升是指是内部状态可由调用他的函数控制的方法。可以通过受控制的可组合函数
    的参数公开状态并从控制可组合函数的外部实例化它来实现此目的。使状态可提升可避免
    重复状态和引入搓无，有利于重用可组合无，并使可组合无更易于测试。可组合调用者
    不感兴趣的状态应该是内部状态

    在某些情况下，消费者可能不关心某个状态（例如：在滚动条中，scrollerPosition状态
    是公开的，而maxPosition）  真相的来源属于创造和控制那个状态的人

    示例中，消费者counter可能对状态感兴趣，因此你可以通过引入（count，updateCount）
    对作为的参数将其完全推迟给调用者Counter，这样Counter就是吊装它的状态：*/

    @Composable
    fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
        val counterState = remember { mutableStateOf(0) }

        Column {
            for (name in names) {
                Greeting(name = name)
                Divider(color = Color.Black)
            }
            Divider(color = Color.Transparent, thickness = 32.dp)
            Counter(
                count = counterState.value,
                updateCount = { newCount ->
                    counterState.value = newCount
                }
            )
        }
    }

    @Composable
    fun Counter(count: Int, updateCount: (Int) -> Unit) {
        Button(onClick = { updateCount(count+1) }) {
            Text("I've been clicked $count times")
        }
    }

    /*保存状态的几种方式
    通过上文我们知道通常状态是使用State<T>来保存的，在具体开发时通常有一下几种方式声明
    mutablesStateOf：
    ·val mutableSate = remember{mutableStateOf(default)}
    ·val value by remember{mutableStateOf(default)}
    ·val (value,setValue) = remember{mutableStateOf(default)}
    注意时委托用法by需要导入相关的类

    */

    /*ViewModel与State
    * 最好将状态与UI进行分离。因此可以考虑使用viewModel来进行State管理
    * 示例：
    * @Composable
private fun TodoActivityScreen(todoViewModel: TodoViewModel) {
   TodoScreen(
       items = todoViewModel.todoItems,
       onAddItem = todoViewModel::addItem,
       onRemoveItem = todoViewModel::removeItem
   )
}
*
* import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel

class TodoViewModel : ViewModel() {

    private var currentEditPosition by mutableStateOf(-1)

    var todoItems = mutableStateListOf<TodoItem>()
        private set

    val currentEditItem: TodoItem?
        get() = todoItems.getOrNull(currentEditPosition)

    fun addItem(item: TodoItem) {
        todoItems.add(item)
    }

    fun removeItem(item: TodoItem) {
        todoItems.remove(item)
        onEditDone() // don't keep the editor open when removing items
    }

    fun onEditItemSelected(item: TodoItem) {
        currentEditPosition = todoItems.indexOf(item)
    }

    fun onEditDone() {
        currentEditPosition = -1
    }

    fun onEditItemChange(item: TodoItem) {
        val currentItem = requireNotNull(currentEditItem)
        require(currentItem.id == item.id) {
            "You can only change an item with the same id as currentEditItem"
        }

        todoItems[currentEditPosition] = item
    }
}
*
*/

    /*LiveData转换为State
    * Jetpack Compose并不要求你使用MutableState<T>存储状态。Jetpack Compose支持
    * 其他可观察类型。在JetPack Compose中读取其他可观察你类型之前，必须将其
    * 转换为State<T>,以便JetpackCompose可以在状态发生变化时自动重组界面
    * Compose附带一些可以根据Android应用中使用的常见可观察类型创建
    * State<T>,以便Jetpack Compose可以在状态发生改变时自动重组界面
    * liveDatat示例：
    * class HelloViewModel : ViewModel() {

    // LiveData holds state which is observed by the UI
    // (state flows down from ViewModel)
    private val _name = MutableLiveData("")
    val name: LiveData<String> = _name

    // onNameChange is an event we're defining that the UI can invoke
    // (events flow up from UI)
    fun onNameChange(newName: String) {
        _name.value = newName
    }
}

@Composable
fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {
    // by default, viewModel() follows the Lifecycle as the Activity or Fragment
    // that calls HelloScreen(). This lifecycle can be modified by callers of HelloScreen.

    // name is the current value of [helloViewModel.name]
    // with an initial value of ""
    val name: String by helloViewModel.name.observeAsState("")
    HelloContent(name = name, onNameChange = { helloViewModel.onNameChange(it) })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.h5
        )
        OutlinedTextField(
            value = name,
            onValueChange = onNameChange,
            label = { Text("Name") }
        )
    }
}

*
* val name: String by helloViewModel.name.observeAsState("")
上文等效
* val nameState: State<String> = helloViewModel.name.observeAsState("")
    * */

    /*状态保存
    在重新创建activity或进程后，您可以使用rememberSaveable恢复界面状态。
    rememberSaveable可以在重组后保持状态。此外，rememberSaveable也可以在
    重新创建activity和进程后保持状态

    存储状态的方式:
    添加到Bundle的所有数据类型都会自动保存。如果要保存无法添加到Bundle的内容，
    有以下几种选择。

    Parcelize
    最简单的解决方案是向对象添加@parcelize注解。对象将变为可打包状态并且可以捆绑。
    示例：
    */
}

@Parcelize
data class City(val name: String, val country: String) : Parcelable

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

/*MapSaver
* 如果某种原因导致@Parcelize不合适，可以使用mapSaver定义自己的规则，规定如何将对象
* 转换为系统可保存到Bundle的一组值*/
data class City2(val name: String, val country: String)

val CitySaver = run {
    val nameKey = "Name"
    val countryKey = "Country"
    mapSaver(
        save = { mapOf(nameKey to it.name, countryKey to it.country) },//这里面的内容由restore里面的对象进行确定
        restore = { City2(it[nameKey] as String, it[countryKey] as String) }
    )
}

@Composable
fun CityScreen2() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City2("Madrid", "Spain"))
    }
}

/*ListSaver
* 为了避免需要为映射定义键，也可以使用listSaver并将其索引用作键*/
data class City3(val name: String, val country: String)

val CitySaver3 = listSaver<City3, Any>(
    save = { listOf(it.name, it.country) },
    restore = { City3(it[0] as String, it[1] as String) }
)

@Composable
fun CityScreen3() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver3) {
        mutableStateOf(City3("Madrid", "Spain"))
    }
}

/*remember
* 为了避免每次重组时候都生成新的状态，可以使用remember对状态进行缓存，
* remember示例:
* remember() { mutableStateOf(default) }
* 另外这个还可以通过key来判断是否根据key来生成新值，如果没有key的话，则
* 始终返回重组时候生成的新值
* remember(todo2.id){randomTint()}
*
* 记住调用有两个部分：
* 1.关键参数——这个记忆使用的关键，这是在括号中传递的部分。在这里，
*   todo2.id作为关键传递
* 2.计算-一个计算要记住的新值得lambda，在尾随lambda中传递，这里用一个
*   随机值randomTint()
*/

/*状态改变导致UI重组
* 在Compose的设计中，整个UI树会随着状态的改变而进行UI的重绘制。
* 根据底层算法的判断，每次会重绘发生改变的部分，这样就使得UI能够及时响应
* 示例：
*     @Composable
    fun counter() {
        val count = mutableStateOf(0)
        Button(onClick = { count.value++ }) {
            Log.e("Ym","重组Text数据")
            Text("I've been clicked ${count.value} times")
        }
        Log.e("Ym","重组数据")
    }
通过日志可以发现，真的counter()函数只执行了一次，而Button里面的函数，
* 每次点击都会执行，然后Text重新绘制，页面上的数量也跟着增加了
* 如果把Button里面日志中的${count。value}去掉，则会发现不管点击几次，
* Button里面的函数都不会执行。
* 示例：
* @Composable
fun counter() {
    val count = mutableStateOf(0)
    Button(onClick = { count.value++ }) {
        Log.e("Ym","重组Text数据")
        Text("I've been clicked times")
    }
    Log.e("Ym","重组数据")
}
如果在text和Log.e（"Ym","重组数据"）中都加上${count.value}
* 变成以下方式,则发现,每次点击时候使数量增加时候,不管是Button还是counter()
* 函数都重新执行了
* 示例:
* @Composable
fun counter() {
    val count = mutableStateOf(0)
    Button(onClick = { count.value++ }) {
        Log.e("Ym","重组Text数据")
        Text("I've been clicked ${count.value} times")
    }
    Log.e("Ym","重组数据${count.value}")
}
因此证明官方的State<T>数据的监听会导致页面重绘制
* 然而上文在运行后发现不管点击几次数量都是0,这是因为每次重绘制都会导致
* val count =  mutableStateOf(0)重新执行，然后数量就归0了。这时候就
* 可以使用remember来对状态进行缓存，或者将该组合变成无状态组合，
* 将状态进行提升到更高的组合中去
* var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
    Log.e("Ym","重组Text数据")
    Text("I've been clicked $count times")
}
Log.e("Ym","重组数据 $count")

* */

/*重组的最小范围
        在Compose中重组的范围主要收到两种因素影响
        1.只有会收到state变化影响的代码块才会参与到重组，
        不依赖state的代码不参与重组
        2.只有非inLine函数才有资格成为重组的最小范围
   这是因为Compose在编译期分析会收到某state变化影响的代码块，
   并记录其引用，当此State变化时，会根据引用找到这些代码块并标记为
   Invalid。在下一渲染帧到来之前Compose会触发recomposition，
   并在重组过程中执行invalid代码块
   Invalid代码块即编译器找出的下次重组范围。能够被标记为Invalid的
   代码必须是非inLine且无返回值的@Composable function/lambda,
   必须遵循重组范围最小化原则。

为什么inline函数无法编译，因为kotlin编译后，被inline标记的函数会移动到
调用函数的里面，所以编译后的源码是没有inline函数的

而其中Column，Row，box乃至Layout这种容器类composable都是inline函数。
示例：
@Composable
fun Foo() {

    var text by remember { mutableStateOf("") }

    Button(onClick = { text = "$text $text" }) {
        Log.d(TAG, "Button content lambda")
        Box {
            Log.d(TAG, "Box")
            Text(text).also { Log.d(TAG, "Text") }
        }
    }
}
        */

/*缩小重组范围
        根据上文知道最小重组最小范围是非inline函数。那么如果想使其被inline标记的组合
        缩小重组范围，如何做呢，可以使用Wrapper来进行处理
        示例：*/
@Composable
fun Foo() {

    var text by remember { mutableStateOf("") }

    Button(onClick = { text = "$text $text" }) {
        Log.d(TAG, "Button content lambda")
        Wrapper {
            Text(text).also { Log.d(TAG, "Text") }
        }
    }
}

@Composable
fun Wrapper(content: @Composable () -> Unit) {
    Log.d(TAG, "Wrapper recomposing")
    Box {
        Log.d(TAG, "Box")
        content()
    }
}
/*参考链接：
*     Compose基础-状态

https://developer.android.google.cn/codelabs/jetpack-compose-basics#4

    管理状态

https://developer.android.google.cn/jetpack/compose/state?hl=zh-cn

    包裹

https://github.com/Kotlin/KEEP/blob/master/proposals/extensions/android-parcelable.md

    在 Jetpack Compose 中使用状态

https://developer.android.google.cn/codelabs/jetpack-compose-state#0

    记住

https://developer.android.google.cn/reference/kotlin/androidx/compose/runtime/package-summary#remember(kotlin.Function0)

    了解Compose的重组作用域

    https://compose.net.cn/principle/recomposition_scope/

* */






